package org.jeecg.cn.edu.scau.vm.container.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.jeecg.cn.edu.scau.vm.container.domain.VmContainerUsage;
import org.jeecg.cn.edu.scau.vm.container.mapper.VmContainerUsageMapper;
import org.jeecg.cn.edu.scau.vm.container.service.VmContainerUsageService;
import org.jeecg.cn.edu.scau.vm.exception.UpdateMachineTimeException;
import org.jeecg.cn.edu.scau.vm.machineTime.entity.VmUserSettings;
import org.jeecg.cn.edu.scau.vm.machineTime.service.IVmUserSettingsService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Service
public class VmContainerUsageServiceImpl
        extends ServiceImpl<VmContainerUsageMapper, VmContainerUsage>
        implements VmContainerUsageService {

    private static final Logger logger = LoggerFactory.getLogger(VmContainerUsageServiceImpl.class);

    @Resource
    private VmContainerUsageMapper usageMapper;

    @Resource
    private IVmUserSettingsService vmUserSettingsService;

    @Resource
    private ISysUserService userService;

    @Override
    public Boolean updateRemainIngTimeSafely(String userId, Integer remainingTime,
                                             Integer targetTime, Integer retryTimes)
            throws UpdateMachineTimeException {
        logger.info("更新用户剩余机时配额:userId:{},remainingTime:{},targetTime:{}", userId, remainingTime, targetTime);
        while ((retryTimes--) > 0) {
            if (usageMapper.updateRemainIngTimeSafely(userId, remainingTime, targetTime, LocalDateTime.now()) >= 1) {
                return Boolean.TRUE;
            }
            remainingTime = this.getRemainIngTime(userId);
        }
        throw new UpdateMachineTimeException("");
    }

    @Override
    public Boolean updateRemainIngTimeSafely(String userId, Integer targetTime, Integer retryTimes)
            throws UpdateMachineTimeException {
        // 先执行一次查询
        Integer remainingTime = this.getRemainIngTime(userId);
        return updateRemainIngTimeSafely(userId, remainingTime, targetTime, retryTimes);
    }

    @Override
    public Boolean addRemainIngTime(String userId, Integer targetTime, Integer retryTimes)
            throws UpdateMachineTimeException {
        Integer remainIngTime = this.getRemainIngTime(userId);
        logger.info("更新用户剩余机时配额:userId:{},remainingTime:{},targetTime:{}", userId, remainIngTime, targetTime);
        while ((retryTimes--) > 0) {
            if (usageMapper.updateRemainIngTimeSafely(userId, remainIngTime, remainIngTime + targetTime, LocalDateTime.now()) >= 1) {
                return Boolean.TRUE;
            }
            remainIngTime = this.getRemainIngTime(userId);
        }
        throw new UpdateMachineTimeException("更新用户配额失败,产生多并发竞争,进入消息队列处理");
    }

    @Override
    public Boolean minusRemainIngTime(String userId, Integer targetTime, Integer retryTimes) throws UpdateMachineTimeException {
        Integer remainIngTime = this.getRemainIngTime(userId);
        logger.info("更新用户剩余机时配额:userId:{},remainingTime:{},targetTime:{}", userId, remainIngTime, targetTime);
        while ((retryTimes--) > 0) {
            if (usageMapper.updateRemainIngTimeSafely(userId, remainIngTime, remainIngTime - targetTime, LocalDateTime.now()) >= 1) {
                return Boolean.TRUE;
            }
            remainIngTime = this.getRemainIngTime(userId);
        }
        throw new UpdateMachineTimeException("");
    }

    @Override
    public Integer getRemainIngTime(String userId) {
        QueryWrapper<VmContainerUsage> vmContainerUsageQueryWrapper = new QueryWrapper<>();
        vmContainerUsageQueryWrapper.eq("user_id", userId);
        return this.getOne(vmContainerUsageQueryWrapper).getRemainIngTime();
    }


    @Override
    public Boolean tryAllocateMachineTime(LoginUser loginUser, Integer machineTime) {
        Boolean result = Boolean.TRUE;
        // 1.1 获取用户当前机时
        try {
            QueryWrapper<VmContainerUsage> containerUsageWrapper = new QueryWrapper<>();
            containerUsageWrapper.eq("user_id", loginUser.getId());
            /*如果此时还没有用户机时相关配置,创建配置*/
            VmContainerUsage vmContainerUsage = this.getOne(containerUsageWrapper);
            if (Objects.isNull(vmContainerUsage)) {
                logger.info("没有为用户{}创建机时配置,正在创建", loginUser.getId());
                vmContainerUsage = this.createUsageForUser(loginUser);
                if (Objects.isNull(vmContainerUsage)) {
                    logger.info("创建用户{}机时配置失败!", loginUser.getId());
                    return Boolean.FALSE;
                }
                vmContainerUsage = this.createUsageForUser(loginUser);
            }
            // 1.2 判断机时间是否足够
            Integer remainIngTime = vmContainerUsage.getRemainIngTime();
            int targetTime = remainIngTime - machineTime;
            if (targetTime < 0) {
                logger.error("当前机时不足!可用机时为:" + remainIngTime + "分钟" + ",当前申请机时为:" + machineTime + "分钟");
                return Boolean.FALSE;
            }
            // 1.3 机时足够,尝试分配,如果发生并发,则自动重试五次
            if (!this.minusRemainIngTime(loginUser.getId(), machineTime, 5)) {
                logger.error("当前机时不足!可用机时为:" + remainIngTime + "分钟" + ",当前申请机时为:" + machineTime + "分钟");
                return Boolean.FALSE;
            }
        } catch (UpdateMachineTimeException e) {
            logger.error("更新机时间失败{}", e.getMessage(), e);
            result = Boolean.FALSE;
        }
        return result;
    }


    @Override
    public VmContainerUsage createUsageForUser(LoginUser loginUser) {
        try {
            // 1. 先读取默认配置
            VmUserSettings userSetting = vmUserSettingsService.getByUserId(loginUser.getId());
            // 2. 如果没有配置,则新建
            if (Objects.isNull(userSetting)) {
                userSetting = vmUserSettingsService.createUserSetting(loginUser);
            }
            // 3. 此时用户配置修改完毕,执行创建
            VmContainerUsage vmContainerUsage = new VmContainerUsage();
            vmContainerUsage.setUserId(loginUser.getId());
            vmContainerUsage.setLastDistributionTime(LocalDateTime.now());
            vmContainerUsage.setRemainIngTime(userSetting.getMachineTimeAllocate());
            vmContainerUsage.setRemainGpuCardNumber(userSetting.getGpuCardNum());
            this.save(vmContainerUsage);
            logger.info("创建用户配置成功!{}", vmContainerUsage);
            return vmContainerUsage;
        } catch (Exception e) {
            logger.error("产生异常!{}", e.getMessage(), e);
        }
        return null;
    }

    @Override
    public Map<String, VmContainerUsage> getUsageByIds(List<String> userIds) {
        if (Objects.isNull(userIds) || userIds.isEmpty()) {
            return new HashMap<>();
        }
        Map<String, VmContainerUsage> map = new HashMap<>(userIds.size());
        userIds.forEach((id) -> map.put(id, getByUserId(id)));
        return map;
    }

    @Override
    public VmContainerUsage getByUserId(String userId) {
        QueryWrapper<VmContainerUsage> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        return this.getOne(queryWrapper);
    }

    @Override
    public Result<VmContainerUsage> getLoginUserUsage() {
        // 1. 获取登录的用户
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (Objects.isNull(loginUser)) {
            return Result.error(null);
        }
        // 2. 获取本月配额
        VmContainerUsage userUsage = this.getByUserId(loginUser.getId());
        return Result.ok(userUsage);
    }

    @Override
    public void syncData() {
        // 1. 获取所有用户,做一个分页查询
        List<SysUser> list = userService.list();
        // 2. 检查是否达到时间
        for (SysUser sysUser : list) {
            // 2.1 找出此用户的机时使用情况
            String userId = sysUser.getId();
            QueryWrapper<VmContainerUsage> usageQueryWrapper = new QueryWrapper<>();
            usageQueryWrapper.eq("user_id", userId);
            VmContainerUsage vmContainerUsage = this.getOne(usageQueryWrapper);
            // 2.2 检查距离上次发放是否超过了一个月,超过则执行刷新
            if (Objects.isNull(vmContainerUsage)) {
                // 如果为空,则创建配额
                LoginUser loginUser = new LoginUser();
                loginUser.setId(userId);
                this.createUsageForUser(loginUser);
                continue;
            } else if (vmContainerUsage.getLastDistributionTime().plusMonths(1).isBefore(LocalDateTime.now())) {
                continue;
            }
            try {
                // 否则刷新
                QueryWrapper<VmUserSettings> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("user_id", userId);
                VmUserSettings settings = vmUserSettingsService.getOne(queryWrapper);
                if (this.updateRemainIngTimeSafely(userId, vmContainerUsage.getRemainIngTime(), settings.getMachineTimeAllocate(), 5)) {
                    logger.info("更新用户{}机时为:{}成功", userId, settings.getMachineTimeAllocate());
                }
            } catch (UpdateMachineTimeException e) {
                //TODO: 兜底方案,基于消息队列实现
                logger.error("更新失败,进入消息队列处理流程...");
            }
        }
    }
}
